package day27.Thread;

public class ThreadCommunicationTest {
    public static void main(String[] args) {
        System.out.println("=== Java线程间通信测试 ===\n");
        
        // 创建共享对象
        SharedData sharedData = new SharedData();
        
        // 创建生产者和消费者线程
        Thread producer = new Thread(new Producer(sharedData), "Producer");
        Thread consumer = new Thread(new Consumer(sharedData), "Consumer");
        
        // 启动线程
        producer.start();
        consumer.start();
        
        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("生产者和消费者测试完成");
    }
}

// 共享数据类 - 生产者和消费者共享的数据结构
class SharedData {
    private int data;           // 实际存储的数据
    private boolean hasData = false;  // 标志位，表示当前是否有未消费的数据
    
    // 生产数据的方法 - 使用synchronized关键字保证线程安全
    public synchronized void produce(int value) {
        // 如果已经有数据，则等待消费者消费
        // 使用while循环而不是if判断是为了防止虚假唤醒问题
        while (hasData) {
            try {
                // 调用wait()方法使当前线程进入等待状态，并释放对象锁
                // 其他线程可以进入该同步代码块
                // wait()方法会释放当前对象的锁，使其他线程可以获取该锁
                // 当前线程进入等待队列，直到被其他线程唤醒
                wait(); // 等待消费者消费数据
            } catch (InterruptedException e) {
                // 当线程在等待期间被中断时会抛出InterruptedException
                e.printStackTrace();
            }
        }
        
        // 生产数据 - 当前线程获得对象锁且hasData为false时执行
        this.data = value;
        this.hasData = true;
        System.out.println("生产者生产数据: " + value);
        
        // 通知等待的消费者线程 - 调用notify()唤醒在此对象监视器上等待的单个线程
        // notify()方法会随机唤醒一个在该对象上等待的线程
        // 唤醒的线程会重新尝试获取对象锁，获取成功后从wait()方法处继续执行
        notify();
    }
    
    // 消费数据的方法 - 使用synchronized关键字保证线程安全
    public synchronized int consume() {
        // 如果没有数据，则等待生产者生产
        // 使用while循环而不是if判断是为了防止虚假唤醒问题
        // 虚假唤醒是指线程在没有被notify/notifyAll唤醒的情况下自动苏醒
        while (!hasData) {
            try {
                // 调用wait()方法使当前线程进入等待状态，并释放对象锁
                // 使其他线程可以进入同步代码块进行生产操作
                wait(); // 等待生产者生产数据
            } catch (InterruptedException e) {
                // 当线程在等待期间被中断时会抛出InterruptedException
                e.printStackTrace();
            }
        }
        
        // 消费数据 - 当前线程获得对象锁且hasData为true时执行
        hasData = false;
        System.out.println("消费者消费数据: " + data);
        
        // 通知等待的生产者线程 - 调用notify()唤醒在此对象监视器上等待的单个线程
        // 此时hasData已设为false，生产者线程可以继续生产数据
        notify();
        
        return data;
    }
}

// 生产者任务 - 实现Runnable接口的生产者线程任务
class Producer implements Runnable {
    private SharedData sharedData;  // 引用共享数据对象
    
    public Producer(SharedData sharedData) {
        this.sharedData = sharedData;
    }
    
    @Override
    public void run() {
        // 生产5个数据
        for (int i = 1; i <= 5; i++) {
            // 调用共享对象的produce方法生产数据
            sharedData.produce(i);
            
            try {
                // 模拟生产时间 - 让线程休眠1秒，模拟实际生产需要时间
                // 通过休眠来模拟生产数据需要耗费一定时间的场景
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者任务 - 实现Runnable接口的消费者线程任务
class Consumer implements Runnable {
    private SharedData sharedData;  // 引用共享数据对象
    
    public Consumer(SharedData sharedData) {
        this.sharedData = sharedData;
    }
    
    @Override
    public void run() {
        // 消费5个数据
        for (int i = 1; i <= 5; i++) {
            // 调用共享对象的consume方法消费数据
            sharedData.consume();
            
            try {
                // 模拟消费时间 - 让线程休眠1.5秒，模拟实际消费需要时间
                // 消费时间比生产时间长，可以看到生产者等待消费者的效果
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}